package crp.extensions.xjtu.sso;

import com.qwlabs.jackson.Jackson;
import crp.core.security.domain.messages.SecurityMessages;
import crp.core.security.domain.models.CallerPrincipal;
import crp.core.security.domain.models.SSOAuthenticationRequest;
import crp.core.security.domain.services.SSOIdentityProvider;
import com.qwlabs.cdi.Dispatchable;
import com.qwlabs.exceptions.ExternalException;
import crp.core.user.domain.models.UserEntity;
import crp.extensions.xjtu.XJTU;
import crp.extensions.xjtu.sso.models.XJTUUserInfo;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@ApplicationScoped
@Slf4j
public class XJTUSSOIdentityProvider implements Dispatchable<String>, SSOIdentityProvider {

    private final XJTUSSORestClient restClient;
    private final XJTUSSOUserPersistence userPersistence;

    @Inject
    public XJTUSSOIdentityProvider(@RestClient XJTUSSORestClient restClient,
                                   XJTUSSOUserPersistence userPersistence) {
        this.restClient = restClient;
        this.userPersistence = userPersistence;
    }

    @Override
    public Uni<SecurityIdentity> authenticate(SSOAuthenticationRequest request,
                                              AuthenticationRequestContext context) {
        return Uni.createFrom().item(this.authenticate(request));
    }

    private SecurityIdentity authenticate(SSOAuthenticationRequest request) {
        XJTUUserInfo userInfo = loadUserInfo(request);
        UserEntity user = userPersistence.persist(userInfo)
                .orElseThrow(() -> SecurityMessages.INSTANCE.ssoLoginFailed(request.getProvider()));
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(CallerPrincipal.fromUser(user.getId()));
        return builder.build();
    }

    private XJTUUserInfo loadUserInfo(SSOAuthenticationRequest request) {
        String code = request.getAttribute("code");
        try {
            String tokenResponse = restClient.getAccessToken(code);
            XJTUAccessTokenResponse accessTokenResponse = Jackson.read(tokenResponse, XJTUAccessTokenResponse.class)
                    .orElseThrow(() -> new ExternalException("invalid response:" + tokenResponse));
            String infoResponse = restClient.getUserInfo(accessTokenResponse.getData().getAccessToken());
            XJTUUserInfoResponse userInfoResponse = Jackson.read(infoResponse, XJTUUserInfoResponse.class)
                    .orElseThrow(() -> new ExternalException("invalid response:" + infoResponse));
            return userInfoResponse.getData();
        } catch (ExternalException e) {
            throw e;
        } catch (Exception e) {
            LOGGER.error("invalid response.");
            throw new ExternalException("invalid response.");
        }
    }

    @Override
    public boolean dispatchable(String context) {
        return XJTU.ENV.equals(context);
    }
}
